﻿using System;
using System.Collections.Specialized;
using System.Configuration;
using System.IO.Ports;
using System.Text.RegularExpressions;
using System.Threading;
using UDS.Management.SMS;

namespace UDS.Management.SMSv2
{
    internal class NativeSmsSender : ISmsSender
    {
        private struct ConfigValues
        {
            public int Port;
            public int BaudRate;
            public Parity Parity;
            public int DataBits;
            public StopBits StopBits;

            /// <summary>
            /// The time to wait before calling SMSQuery to query SMS status.
            /// </summary>
            public int FirstQueryWaitMS;

            /// <summary>
            /// The interval in milliseconds between two SMSQuery call.
            /// </summary>
            public int QueryIntevalMS;

            /// <summary>
            /// The time to wait before calling SMSSendMessage to send SMS.
            /// </summary>
            public int SleepMSBeforeSendingSMS;
        }

        private ConfigValues _configValues;

        /// <summary>
        /// Create the SmsSender, and initialize the SerialPort with the configured information.
        /// </summary>
        public NativeSmsSender(NameValueCollection appSettings)
        {
            // Create an instance of SerialPort
            string portName = appSettings["SerialPort_PortName"];
            _configValues.Port = GetPortNumber(portName);
            _configValues.BaudRate = int.Parse(appSettings["SerialPort_BaudRate"]);
            _configValues.Parity = (Parity)Enum.Parse(typeof(Parity), appSettings["SerialPort_Parity"]);
            _configValues.DataBits = int.Parse(appSettings["SerialPort_DataBits"]);
            _configValues.StopBits = (StopBits)Enum.Parse(typeof(StopBits), appSettings["SerialPort_StopBits"]);

            _configValues.SleepMSBeforeSendingSMS = GetConfigValue(appSettings, "SMS_SleepMSBeforeSendingSMS", 3000);
            _configValues.FirstQueryWaitMS = GetConfigValue(appSettings, "SMS_FirstQueryWaitMS", 3000);
            _configValues.QueryIntevalMS = GetConfigValue(appSettings, "SMS_QueryIntervalMS", 1000);
        }

        void ISmsSender.Open()
        {
            Logger.WriteLine("Starting SMS service");
            Logger.WriteLine(string.Format("Service parameters: SleepMSBeforeSendingSMS={0}, FirstQueryWaitMS={1}, QueryIntevalMS={2}", _configValues.SleepMSBeforeSendingSMS, _configValues.FirstQueryWaitMS, _configValues.QueryIntevalMS));
            int r = NativeMethods.SMSStartService(
                _configValues.Port, _configValues.BaudRate,
                2, 8, 0, 0, "card");
            if (r != 1)
            {
                throw new InvalidOperationException("Failed to start SMS service.");
            }
            Logger.WriteLine("SMS service has been successfully started!");
        }

        bool ISmsSender.SendSMS(string target, string msg)
        {
            if (string.IsNullOrEmpty(target))
            {
                throw new ArgumentNullException("target");
            }
            if (string.IsNullOrEmpty(msg))
            {
                throw new ArgumentNullException("msg");
            }

            // Remove +86 prefix.
            if (target.Length == 13)
            {
                target = target.Substring(3);
            }

            const int MaxLenPerMsg = 70;
            if (msg.Length <= MaxLenPerMsg)
            {
                return SendSingleSMS(target, msg);
            }
            else
            {
                int pos = 0;
                bool succeeded = true;
                while (pos < msg.Length)
                {
                    int len = Math.Min(msg.Length - pos, MaxLenPerMsg);
                    if (!SendSingleSMS(target, msg.Substring(pos, len)))
                    {
                        succeeded = false;
                    }

                    pos += len;
                }

                return succeeded;
            }
        }

        public void Close()
        {
            Logger.WriteLine("Stopping SMS service");
            NativeMethods.SMSStopSerice();
        }

        public void Dispose()
        {
            Close();
        }

        private bool SendSingleSMS(string target, string msg)
        {
            Logger.WriteLine(string.Format("Send SMS to {0}: {1}", target, msg));

            if (_configValues.SleepMSBeforeSendingSMS > 0)
            {
                Thread.Sleep(_configValues.SleepMSBeforeSendingSMS);
            }
            uint msgIndex = NativeMethods.SMSSendMessage(msg, target);
            if (msgIndex == 0)
            {
                throw new InvalidOperationException("Unexpected message index.");
            }

            if (_configValues.FirstQueryWaitMS > 0)
            {
                Thread.Sleep(_configValues.FirstQueryWaitMS);
            }

            int returnValue;
            int numWait = 30;
            while (numWait > 0)
            {
                returnValue = NativeMethods.SMSQuery(msgIndex);

                switch (returnValue)
                {
                    case 0:
                        Logger.WriteLine("Send result: Failed", Severity.Error);
                        return false;

                    case 1:
                        Logger.WriteLine("Send result: Succeeded", Severity.Information);
                        return true;

                    default:
                        Logger.WriteLine(string.Format("SMSQuery returns {0}.", returnValue));
                        break;
                }                

                numWait--;
                if (numWait > 0 && _configValues.QueryIntevalMS > 0)
                {
                    Thread.Sleep(_configValues.QueryIntevalMS);
                }
            }

            Logger.WriteLine("Send result: Timed out!", Severity.Error);
            return false;
        }

        private static int GetPortNumber(string portName)
        {
            Regex regex = new Regex(
                @"^\s*COM(\d+)\s*$",
                RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase);
            Match match = regex.Match(portName);
            if (match.Success)
            {
                return int.Parse(match.Groups[1].Value);
            }

            throw new ConfigurationErrorsException(string.Format("Invalid setting for 'SerialPort_PortName': '{0}'", portName));
        }

        private static int GetConfigValue(NameValueCollection appSettings, string key, int defaultValue)
        {
            string keyValue = appSettings[key];
            if (string.IsNullOrEmpty(keyValue))
            {
                return defaultValue;
            }

            int value;
            if (int.TryParse(keyValue, out value) && value >= 0)
            {
                return value;
            }
            throw new ConfigurationErrorsException(string.Format("Invalid setting for '{0}': {1}.", key, keyValue));
        }
    }
}
